#ifndef __DB_MANAGER_H__
#define __DB_MANAGER_H__

#include <iostream>
#include <string>
#include <mutex> // 引入 once_flag
#include <thread>
#include <soci/soci.h>
#include <soci/mysql/soci-mysql.h>
#include "../log/xdgspdlog.hpp" // 日志文件


/**
 * @class DBManager
 * @brief 数据库连接池管理类 (单例模式)。
 *
 * 该类使用单例模式，确保在整个应用程序中只有一个数据库连接池实例。
 * 它提供了线程安全的初始化和连接获取方法，旨在简化多线程环境下的数据库操作。
 */
class DBManager {
public:
    /**
     * @brief 删除拷贝构造函数。
     * @details 这是为了防止类的实例被拷贝，从而保证单例的唯一性。
     */
    DBManager(const DBManager&) = delete;

    /**
     * @brief 删除赋值操作符。
     * @details 这是为了防止类的实例被赋值，从而保证单例的唯一性。
     */
    DBManager& operator=(const DBManager&) = delete;

    /**
     * @brief 初始化数据库连接池。
     * @details 这是一个线程安全的函数，在程序启动时应调用一次。
     * 即使在多个线程中同时调用，内部也只会执行一次初始化逻辑。
     * @param connectString SOCI格式的数据库连接字符串。
     * @param poolSize 连接池的大小，默认为10。
     */
    static void init(const std::string& connectString, int poolSize = 10) {
        // std::call_once 保证 do_init 函数在多线程中也只会被执行一次
        std::call_once(init_flag_, &DBManager::do_init, connectString, poolSize);
    }

/**
 * @brief 从连接池获取一个数据库会话(session)。
 * @details 此函数实现了“借用时验证”(Test on Borrow)逻辑。
 * 在返回session之前，它会执行一个轻量级查询("SELECT 1")来验证连接是否仍然有效。
 * 如果连接已失效（例如被MySQL服务器的wait_timeout关闭），它会尝试自动重连。
 * 如果连接池已满，它会阻塞并等待。
 * @return soci::session 一个经过验证的、可用的数据库会话对象。
 * @throw std::runtime_error 如果连接池未被初始化，则抛出异常。
 * @throw soci::soci_error 如果在租借或重连过程中发生不可恢复的SOCI错误。
 */
static soci::session getSession() {
    // 1. 必须先检查初始化状态
    if (pool_ == nullptr) {
        LOG_CONSOLE_ERROR("DBManager has not been initialized. Call init() first.");
        throw std::runtime_error("DBManager has not been initialized. Call init() first.");
    }

    // 2. 无限循环，直到获取到一个 *有效* 的 session
    while (true) {
        try {
            // 3. 尝试从连接池租借一个 session
            soci::session sql(*pool_);

            // 4. --- 心跳检查 (关键所在) ---
            try {
                // 执行一个超轻量级的查询来验证连接
                sql << "SELECT 1";
            }
            catch (const soci::soci_error& e) {
                // 5. 验证失败！连接已陈旧 (Stale)
                LOG_CONSOLE_WARN("Stale connection detected. Attempting reconnect... Error: {}", e.what());
                
                // 6. 尝试重连。这个 reconnect() 会在同一个 session 对象上重新建立连接
                sql.reconnect();
                LOG_CONSOLE_INFO("Connection reconnected successfully.");
                
                // 重连成功后，这个 'sql' 对象现在是有效的。当它在调用方函数结束时被RAII归还到池中时，池中将保存这个新建立的连接。
            }
            
            // 7. 返回这个（刚租借的或刚重连的）有效 session
            return sql;

        } catch (const soci::soci_error& e) {
            // 8. 处理租借 session 时的错误 (主要是 "池已满")
            std::string error_msg = e.what();
            
            // 明确检查 "Pool is full" 错误
            if (error_msg.find("Connection pool is full") != std::string::npos) {
                LOG_CONSOLE_WARN("Connection pool is full, retrying after 100ms...");
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            } else {
                // 如果是其他未预料到的租借错误，则记录并抛出
                LOG_CONSOLE_ERROR("Unexpected SOCI error while leasing session: {}", e.what());
                throw; // 抛出异常，这可能是配置错误等严重问题
            }
        }
    } // end while(true)
}

    /**
     * @brief 构建用于SOCI的MySQL连接字符串。
     * @details 这是一个辅助函数，将分散的连接信息组合成SOCI可识别的格式。
     * @param host 服务器地址 (IP或域名)，默认为 "127.0.0.1"。
     * @param port 服务器端口号，默认为 3306。
     * @param dbName 数据库名称，默认为 "mysql"。
     * @param user 用户名，默认为 "root"。
     * @param password 密码，默认为 "1234"。
     * @return std::string 格式化好的连接字符串。
     */
    static std::string createMySqlConnectString(
        const std::string& host = "127.0.0.1",
        int port = 3306,
        const std::string& dbName = "mysql",
        const std::string& user = "root",
        const std::string& password = "1234"
    ) {
        std::stringstream ss;
        ss << "host=" << host
           << " port=" << port
           << " db=" << dbName
           << " user=" << user
           << " password='" << password << "'"; // 建议将密码放在单引号内以支持特殊字符
        return ss.str();
    }

private:
    /**
     * @brief 私有构造函数。
     * @details 防止外部通过 new DBManager() 的方式创建实例，保证单例模式。
     */
    DBManager() {}

    /**
     * @brief 实际执行初始化的私有函数。
     * @details 此函数不是线程安全的，应只通过 std::call_once 调用。
     * @param connectString 数据库连接字符串。
     * @param poolSize 连接池大小。
     */
    static void do_init(const std::string& connectString, int poolSize) {
        pool_ = std::make_unique<soci::connection_pool>(poolSize);
        for (int i = 0; i < poolSize; ++i) {
            soci::session &sql = pool_->at(i);
            sql.open(soci::mysql, connectString);
        }
    }

    /// @brief 使用智能指针管理的数据库连接池对象。
    static std::unique_ptr<soci::connection_pool> pool_;
    
    /// @brief 用于保证 init() 中的初始化逻辑只执行一次的标志。
    static std::once_flag init_flag_;
};

// --- 静态成员变量的类外定义和初始化 ---
std::unique_ptr<soci::connection_pool> DBManager::pool_ = nullptr;
std::once_flag DBManager::init_flag_;


/**
 * @brief 查询指定用户是否存在于数据库中。
 * @param username 要查询的用户名。
 * @return std::string 如果用户存在，返回其 u_salt 字段；如果用户不存在，返回空字符串。
 */
inline std::string DBqueryUserExists(const std::string username) {
    try {
        soci::session sql = DBManager::getSession();
        std::string result_salt;

        sql << "SELECT u_salt FROM Users WHERE u_name = :name",
            soci::use(username), soci::into(result_salt);

        // 检查查询是否找到了数据行
        if (sql.got_data()) {
            return result_salt;
        } else {
            return "";
        }
    } catch (const std::exception& e) {
        LOG_CONSOLE_ERROR("Error in queryUserExists: {}", e.what()); // 发生数据库错误时也返回空字符串，并打印日志
        return "";
    }
}


/**
 * @brief 向数据库中注册一个新用户。
 * @param username 用户名。
 * @param pwd_hash 加盐哈希后的密码。
 * @param salt 用于哈希的盐值。
 * @return bool 如果注册成功，返回 true；如果用户已存在或发生数据库错误，返回 false。
 */
inline bool DBregisterUser(const std::string username, const std::string pwd_hash, const std::string salt) {
    try {
        soci::session sql = DBManager::getSession();
        
        // 由于 u_name 是唯一键，直接插入即可。如果用户名已存在，SOCI会抛出异常。
        sql << "INSERT INTO Users (u_name, u_pwd_hash, u_salt) VALUES (:name, :hash, :salt)",
            soci::use(username), soci::use(pwd_hash), soci::use(salt);
        
        return true;
    } catch (const soci::mysql_soci_error& e) {
        // 特别处理 MySQL 错误，错误码 1062 表示唯一键冲突
        if (std::string(e.what()).find("Duplicate entry") != std::string::npos) {
            LOG_CONSOLE_INFO("Registration failed for user '%s': username already exists.", username.c_str());
        } else {
            LOG_CONSOLE_INFO("Database error during registration for user '%s': %s", username.c_str(), e.what());
        }
        return false;
    } catch (const std::exception& e) {
        LOG_CONSOLE_INFO("Generic error during registration for user '%s': %s", username.c_str(), e.what());
        return false;
    }
}

/**
 * @brief 验证用户的登录信息。
 * @param username 用户名。
 * @param pwd_hash_to_check 需要验证的、已经过前端加盐哈希计算的密码哈希。
 * @return bool 如果用户名和密码哈希匹配，返回 true；否则返回 false。
 */
bool DBverifyUserLogin(const std::string username, const std::string pwd_hash_to_check) {
    try {
        soci::session sql = DBManager::getSession();
        std::string db_pwd_hash;

        sql << "SELECT u_pwd_hash FROM Users WHERE u_name = :name",
            soci::use(username), soci::into(db_pwd_hash);

        // 如果找到了用户并且密码哈希不为NULL
        if (sql.got_data()) {
            return db_pwd_hash == pwd_hash_to_check;
        } else {
            return false; // 用户不存在
        }
    } catch (const std::exception& e) {
        LOG_CONSOLE_INFO("Error in verifyUserLogin for user '%s': %s", username.c_str(), e.what());
        return false;
    }
}












#endif // __DB_MANAGER_H__